﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Mono.Cecil;

namespace cilada.engine.Graph
{
    public static class GraphExtensionMethods
    {
        public static TypeVertex ToTypeVertex(this TypeDefinition td)
        {
            if (td == null)
            {
                throw new ArgumentNullException("The given type definition cannot be null.");
            }

            TypeVertex tv = new TypeVertex(td.FullName);
            tv.Type = GetVertexType(td);
            tv.Namespace = td.Namespace;
            tv.DisplayName = td.Name;
            tv.IsAbstract = td.IsAbstract;
            tv.IsInterface = td.IsInterface;
            tv.IsNestedType = td.IsNested;
            tv.AccessLevel = GetTypeAccessLevel(td);

            return tv;
        }

        public static TypeVertex ToTypeVertex(this TypeDefinition td, string container)
        {
            TypeVertex tv = td.ToTypeVertex();
            tv.Container = container;
            return tv;
        }

        private static TypeVertexTypes GetVertexType(TypeDefinition td)
        {
            // TODO - this will need checking and probably fixing.
            // also need to work out what exactly would identify a custom attribute.

            if(td.IsValueType)
            {
                return TypeVertexTypes.STRUCT;
            }
            else if (td.IsInterface)
            {
                return TypeVertexTypes.INTERFACE;
            }
            else if (td.IsEnum)
            {
                return TypeVertexTypes.ENUM;
            }
            else
            {
                return TypeVertexTypes.CLASS;
            }
        }

        public static MethodVertex ToMethodVertex(this MethodReference mr)
        {
            if (mr == null)
            {
                throw new ArgumentNullException("The given method definition cannot be null.");
            }

            MethodVertex mv = new MethodVertex(mr.FullName);
            mv.DisplayName = mr.Name;
            mv.AccessLevel = (mr is MethodDefinition) ? GetMethodAccessLevel(mr as MethodDefinition) 
                                                      : Visibility.UNKNOWN;

            return mv;
        }
        
        public static AttributeVertex ToAttributeVertex(this PropertyDefinition pd)
        {
            if (pd == null)
            {
                throw new ArgumentNullException("The given property definition cannot be null.");
            }

            AttributeVertex av = new AttributeVertex(pd.FullName);
            av.DisplayName = pd.Name;
            av.AccessLevel = Visibility.UNKNOWN;

            return av;
        }

        private static Visibility GetTypeAccessLevel(TypeDefinition td)
        {
            if (td.IsPublic)
            {
                return Visibility.PUBLIC;
            }
            else if (td.IsNestedPrivate)
            {
                return Visibility.PRIVATE;
            }
            else if (td.IsNestedAssembly)
            {
                return Visibility.CONTAINER;
            }
            else
            {
                return Visibility.PROTECTED;
            }
        }

        private static Visibility GetMethodAccessLevel(MethodDefinition md)
        {
            if (md.IsPrivate)
            {
                return Visibility.PRIVATE;
            }
            else if (md.IsPublic)
            {
                return Visibility.PUBLIC;
            }
            else if (md.IsFamilyAndAssembly) // TODO Is this right? Need a better way of representing.
            {
                return Visibility.PROTECTED | Visibility.CONTAINER;
            }
            else if (md.IsFamilyOrAssembly) // TODO Is this right? Need a better way of representing.
            {
                return Visibility.PROTECTED | Visibility.CONTAINER;
            }
            else if (md.IsFamily)
            {
                return Visibility.PROTECTED;
            }
            else if (md.IsAssembly)
            {
                return Visibility.CONTAINER;
            }
            else
            {
                return Visibility.UNKNOWN;
            }

        }
    }
}
